/*
 * Copyright (C) 2021 XRADIO TECHNOLOGY CO., LTD. All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *    1. Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the
 *       distribution.
 *    3. Neither the name of XRADIO TECHNOLOGY CO., LTD. nor the names of
 *       its contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <string.h>
#include "os.h"
#include "common/cmd/cmd.h"
#include "wifi_device.h"
#include "cmd_hm_net_ap.h"

#define cmd_nitems(a) (sizeof((a)) / sizeof((a)[0]))
#define WIFI_DEVICE_SCAN_RESULT_MAX (30)

static WifiEvent sta_event;
WifiScanInfo g_scan_results[WIFI_DEVICE_SCAN_RESULT_MAX];
static unsigned int scan_num = 0;
static int netId = 0;
static char ScanState = 0;

static enum cmd_status Printf_Scan_Result(unsigned int *num) 
{
    if (WIFI_SUCCESS != GetScanInfoList(g_scan_results, num)) {
        printf("Error : wifi get scan_result fail.\n");
        return CMD_STATUS_FAIL;
    }

    printf("Scan success, results :\n");
    for (int i = 0; i < *num; i++) {
        printf("scan result num %d: ", i + 1);
        printf("ssid: %s ", g_scan_results[i].ssid);
        printf("bssid: ");
        printf("%02X%02X%02X%02X%02X%02X ", g_scan_results[i].bssid[0],
               g_scan_results[i].bssid[1], g_scan_results[i].bssid[2],
               g_scan_results[i].bssid[3], g_scan_results[i].bssid[4],
               g_scan_results[i].bssid[5]);
        printf("securityType: %d ", g_scan_results[i].securityType);
        printf("rssi: %d ", g_scan_results[i].rssi);
        printf("band: %dMHZ ", g_scan_results[i].band);
        printf("frequency: %dMHZ\n", g_scan_results[i].frequency);
    }	
    return CMD_STATUS_OK;
}

static void Scan_done_deal(int state, int size)
{
    if (state == WIFI_STATE_AVALIABLE) {
        printf("======== Callback: scan done, the num of bss: %d\n",
               size);
        if (ScanState == 1) {
            ScanState = 0;
            scan_num = 30;
            Printf_Scan_Result(&scan_num);
        }
    }
}

static void Connected_deal(int state, WifiLinkedInfo *info)
{
    if (state == WIFI_STATE_AVALIABLE) {
        printf("======== Callback: connected\n");

    } else if (state == WIFI_STATE_NOT_AVALIABLE) {
        printf("======== Callback: disconnected\n");
    }
}

WifiEvent *STA_CallBack_Register(void)
{
    return &sta_event;
}

static enum cmd_status cmd_net_sta_enable_exec(char *cmd)
{
    if (IsHotspotActive() == WIFI_HOTSPOT_ACTIVE) {
        if (DisableHotspot() != WIFI_SUCCESS) {
            printf("disable ap fail!\n");
            return ERROR_WIFI_BUSY;
        }
        UnRegisterWifiEvent(AP_CallBack_Register());
    }

    if (WIFI_SUCCESS != EnableWifi()) {
        printf("Error : wifi enable fail.\n");
        return CMD_STATUS_FAIL;
    }

    sta_event.OnWifiScanStateChanged = Scan_done_deal;
    sta_event.OnWifiConnectionChanged = Connected_deal;

    if (WIFI_SUCCESS != RegisterWifiEvent(&sta_event)) {
        printf("Error: RegisterWifiEvent fail\n");
        return CMD_STATUS_FAIL;
    }

    return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_sta_disable_exec(char *cmd)
{
    if (WIFI_SUCCESS != DisableWifi()) {
        printf("Error: DisableWifi fail.\n");
        return CMD_STATUS_FAIL;
    }

    if (WIFI_SUCCESS != UnRegisterWifiEvent(&sta_event)) {
        printf("Error: UnRegisterWifiEvent fail\n");
        return CMD_STATUS_FAIL;
    }

    return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_scan_once_exec(char *cmd)
{
    scan_num = 0;
    memset(&g_scan_results, 0,
           WIFI_DEVICE_SCAN_RESULT_MAX * (sizeof(WifiScanInfo)));

    if (WIFI_SUCCESS != Scan()) {
        printf("Error : wifi scan fail.\n");
        return CMD_STATUS_FAIL;
    }
    ScanState = 1;
    return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_scan_result_exec(char *cmd)
{
    int32_t cnt;

    cnt = cmd_sscanf(cmd, "n=%d", &scan_num);
    if ((cnt != 1) || (scan_num == 0)) {
        return CMD_STATUS_INVALID_ARG;
    }
    if (scan_num > WIFI_DEVICE_SCAN_RESULT_MAX) {
        scan_num = WIFI_DEVICE_SCAN_RESULT_MAX;
    }

    return Printf_Scan_Result(&scan_num);
}

static enum cmd_status cmd_net_add_exec(char *cmd)
{
    char *argv[3];
    WifiDeviceConfig config = { 0 };

    if (cmd_parse_argv(cmd, argv, cmd_nitems(argv)) == 0) {
        return CMD_STATUS_INVALID_ARG;
    }

    memcpy(config.ssid, (uint8_t *)argv[0], cmd_strlen(argv[0]));
    if (cmd_strlen(argv[1])) {
        memcpy(config.preSharedKey, (uint8_t *)argv[1],
               cmd_strlen(argv[1]));
        if (0 == strcmp(argv[2], "wep")) {
            config.securityType = WIFI_SEC_TYPE_WEP;
        } else if (0 == strcmp(argv[2], "psk")) {
            config.securityType = WIFI_SEC_TYPE_PSK;
        } else if (0 == strcmp(argv[2], "sae")) {
            config.securityType = WIFI_SEC_TYPE_SAE;
        } else {
        }
        printf("target ssid %s\n", argv[0]);
        printf("target psk %s\n", argv[1]);
    } else {
        config.securityType = WIFI_SEC_TYPE_OPEN;
        printf("target ssid %s\n", argv[0]);
        printf("target psk empty\n");
    }
    config.wapiPskType = WIFI_PSK_TYPE_ASCII;

    if (WIFI_SUCCESS != AddDeviceConfig(&config, &netId)) {
        printf("Error: AddDeviceConfig Fail\n");
        return CMD_STATUS_FAIL;
    }
    printf("AddDeviceConfig Success.\n");
    return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_auto_connect_exec(char *cmd)
{
    WifiErrorCode error;
    int target = 0;
    int cnt = cmd_sscanf(cmd, "n=%d", &target);
    if (cnt != 1) {
        return CMD_STATUS_INVALID_ARG;
    }

    error = ConnectTo(target);
    if (WIFI_SUCCESS != error) {
        printf("Error %d: ConnectTo Fail\n", error);
        return CMD_STATUS_FAIL;
    }
    printf("ConnectTo Success\n");

    return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_connect_exec(char *cmd)
{
    WifiErrorCode error;
    const char ssid_want_connect[WIFI_MAX_SSID_LEN];
    const char psk[WIFI_MAX_KEY_LEN];
    char *argv[2];

    if (cmd_parse_argv(cmd, argv, cmd_nitems(argv)) == 0) {
        return CMD_STATUS_INVALID_ARG;
    }

    memset(ssid_want_connect, 0, WIFI_MAX_SSID_LEN);
    memcpy(ssid_want_connect, (uint8_t *)argv[0], cmd_strlen(argv[0]));
    if (cmd_strlen(argv[1])) {
        memset(psk, 0, WIFI_MAX_KEY_LEN);
        memcpy(psk, (uint8_t *)argv[1], cmd_strlen(argv[1]));

        printf("target ssid %s\n", argv[0]);
        printf("target psk %s\n", argv[1]);
    } else {
        memset(psk, 0, WIFI_MAX_KEY_LEN);
        printf("target ssid %s\n", argv[0]);
        printf("target psk empty\n");
    }

    WifiDeviceConfig config = { 0 };
    int i = 0;

    for (i = 0; i < scan_num; i++) {
        if (0 == strcmp(g_scan_results[i].ssid, ssid_want_connect)) {
            memcpy(config.ssid, g_scan_results[i].ssid,
                   WIFI_MAX_SSID_LEN);
            memcpy(config.bssid, g_scan_results[i].bssid,
                   WIFI_MAC_LEN);
            config.securityType = g_scan_results[i].securityType;
            config.wapiPskType = WIFI_PSK_TYPE_ASCII;
            strcpy(config.preSharedKey, psk);
            config.freq = g_scan_results[i].frequency;
            break;
        }
    }

    if ((g_scan_results == NULL) || (i >= scan_num)) {
        memcpy(config.ssid, ssid_want_connect, WIFI_MAX_SSID_LEN);	
        if (strlen(psk) == 0) {
            config.securityType = WIFI_SEC_TYPE_OPEN;
        } else {
            strcpy(config.preSharedKey, psk);
            config.securityType = WIFI_SEC_TYPE_PSK;
            config.wapiPskType = WIFI_PSK_TYPE_ASCII;
        }
    }
    printf("GetScanInfoList Success.\n");
    if (WIFI_SUCCESS != AddDeviceConfig(&config, &netId)) {
        printf("Error: AddDeviceConfig Fail\n");
        return CMD_STATUS_FAIL;
    }
    printf("AddDeviceConfig Success.\n");
    error = ConnectTo(netId);
    if (WIFI_SUCCESS != error) {
        printf("Error %d: ConnectTo Fail\n", error);
        return CMD_STATUS_FAIL;
    }
    printf("ConnectTo Success\n");

    return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_device_info_exec(char *cmd)
{
    WifiDeviceConfig result[WIFI_MAX_CONFIG_SIZE];
    int size = 0;
    int res = GetDeviceConfigs(&result, &size);
    if (WIFI_SUCCESS != res) {
        printf("Error: GetDeviceConfigs Fail\n");
    } else {
        if (size > WIFI_MAX_CONFIG_SIZE) {
            size = WIFI_MAX_CONFIG_SIZE;
        }
        printf("GetDeviceConfigs success, result size = %d\n", size);
        for (int i = 0; i < size; i++) {
            printf("ssid : %s \n", result[i].ssid);
            printf("bssid: ");
            printf("%02X%02X%02X%02X%02X%02X \n",
                   result[i].bssid[0], result[i].bssid[1],
                   result[i].bssid[2], result[i].bssid[3],
                   result[i].bssid[4], result[i].bssid[5]);
            printf("psk : %s \n", result[i].preSharedKey);
            printf("securityType : %d \n", result[i].securityType);
            printf("netId : %d \n", result[i].netId);
            printf("freq : %d Mhz\n", result[i].freq);
            printf("wapiPskType : %d \n", result[i].wapiPskType);
            printf("ipType : %d \n", result[i].ipType);
        }
    }
    return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_remove_exec(char *cmd)
{
    WifiErrorCode error;
    int target = 0;
    int cnt = cmd_sscanf(cmd, "n=%d", &target);
    if (cnt != 1) {
        return CMD_STATUS_INVALID_ARG;
    }

    error = RemoveDevice(target);
    if (WIFI_SUCCESS != error) {
        printf("Error %d: remove Fail\n", error);
        return CMD_STATUS_FAIL;
    }
    printf("remove Success\n");

    return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_disconnect_exec(char *cmd)
{
    if (WIFI_SUCCESS != Disconnect()) {
        printf("Error: Disconnect Fail\n");
        return CMD_STATUS_FAIL;
    }

    return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_set_exec(char *cmd)
{
    return CMD_STATUS_OK;
}

static enum cmd_status cmd_net_get_exec(char *cmd)
{
    if (cmd_strcmp(cmd, "state") == 0) {
        if (WIFI_STA_ACTIVE == IsWifiActive()) {
            printf("Wifi is active.\n");
        } else {
            printf("Wifi is passive.\n");
        }
    } else if (cmd_strcmp(cmd, "linkinfo") == 0) {
        WifiLinkedInfo get_linked_res;

        if (WIFI_SUCCESS != GetLinkedInfo(&get_linked_res)) {
            printf("Error: GetLinkedInfo Fail\n");
            return CMD_STATUS_FAIL;
        }
        printf("GetLinkedInfo Success.\n");

        printf("ssid: %s\n", get_linked_res.ssid);
        printf("bssid: ");
        for (int j = 0; j < WIFI_MAC_LEN; j++) {
            printf("%02X", get_linked_res.bssid[j]);
        }
        printf("\n");
        printf("rssi: %d\n", get_linked_res.rssi);
    } else if (cmd_strcmp(cmd, "mac") == 0) {
        unsigned char get_mac_res[WIFI_MAC_LEN];

        if (WIFI_SUCCESS != GetDeviceMacAddress(get_mac_res)) {
            printf("Error: GetDeviceMacAddress Fail\n");
            return CMD_STATUS_FAIL;
        }
        printf("GetDeviceMacAddress Success.\n");
        for (int j = 0; j < WIFI_MAC_LEN - 1; j++) {
            printf("%02X:", get_mac_res[j]);
        }
        printf("%02X\n", get_mac_res[WIFI_MAC_LEN - 1]);
    } else {
    }

    return CMD_STATUS_OK;
}

static const struct cmd_data g_net_scan_cmds[] = {
    { "enable", cmd_net_sta_enable_exec, CMD_DESC("enable command") },
    { "disable", cmd_net_sta_disable_exec, CMD_DESC("disable command") },
    { "scan", cmd_net_scan_once_exec, CMD_DESC("scan once command") },
    { "scan_result", cmd_net_scan_result_exec,
      CMD_DESC("scan result command") },
    { "device_info", cmd_net_device_info_exec,
      CMD_DESC("scan info command") },
    { "add", cmd_net_add_exec, CMD_DESC("scan add command") },
    { "remove", cmd_net_remove_exec, CMD_DESC("scan remove command") },
    { "connect", cmd_net_connect_exec, CMD_DESC("connect command") },
    { "auto_connect", cmd_net_auto_connect_exec,
      CMD_DESC("auto connect command") },
    { "disconnect", cmd_net_disconnect_exec,
      CMD_DESC("disconnect command") },
    { "set", cmd_net_set_exec, CMD_DESC("set command") },
    { "get", cmd_net_get_exec, CMD_DESC("get command") },
};

enum cmd_status cmd_hm_net_sta_exec(char *cmd)
{
    return cmd_exec(cmd, g_net_scan_cmds, cmd_nitems(g_net_scan_cmds));
}
